var lastFrameTime = Date.now() / 1000;
var canvas;
var shader;
var batcher;
var gl;
var mvp = new spine.webgl.Matrix4();
var assetManager;
var skeletonRenderer;
var shapes;
var skeletons = {};
var activeName_;
var animationName_;
var scale_;
var loop_ = true;

function createObject (activeName, animationName, scale, loop) {
    // Setup canvas and WebGL context. We pass alpha: false to canvas.getContext() so we don't use premultiplied alpha when
    // loading textures. That is handled separately by PolygonBatcher.
    canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    var config = { alpha: true };
    gl = canvas.getContext("webgl", config) || canvas.getContext("experimental-webgl", config);
    if (!gl) {
        alert('WebGL is unavailable.');
        return;
    }

    // Create a simple shader, mesh, model-view-projection matrix and SkeletonRenderer.
    shader = spine.webgl.Shader.newTwoColoredTextured(gl);
    batcher = new spine.webgl.PolygonBatcher(gl);
    mvp.ortho2d(0, 0, canvas.width - 1, canvas.height - 1);
    skeletonRenderer = new spine.webgl.SkeletonRenderer(gl);
    shapes = new spine.webgl.ShapeRenderer(gl);
    assetManager = new spine.webgl.AssetManager(gl);

    // Tell AssetManager to load the resources for each model, including the exported .json file, the .atlas file and the .png
    // file for the atlas. We then wait until all resources are loaded in the load() method.
    assetManager.loadText("source/" + activeName + ".json");
    assetManager.loadTextureAtlas("source/" + activeName + ".atlas");

    activeName_ = activeName;
    animationName_ = animationName;
    scale_ = scale;
    loop_ = loop;


    requestAnimationFrame(load);
}

function load () {
    // Wait until the AssetManager has loaded all resources, then load the skeletons.
    if (assetManager.isLoadingComplete()) {
        skeletons[activeName_] = loadSkeleton(activeName_, animationName_, scale_, loop_);
        requestAnimationFrame(render);
    } else {
        requestAnimationFrame(load);
    }
}

function loadSkeleton (activeName, animationName, scale, loop) {
    // Load the texture atlas using name.atlas from the AssetManager.
    atlas = assetManager.get("source/" + activeName + ".atlas");

    // Create a AtlasAttachmentLoader that resolves region, mesh, boundingbox and path attachments
    atlasLoader = new spine.AtlasAttachmentLoader(atlas);

    // Create a SkeletonJson instance for parsing the .json file.
    var skeletonJson = new spine.SkeletonJson(atlasLoader);

    // Set the scale to apply during parsing, parse the file, and create a new skeleton.
    var skeletonData = skeletonJson.readSkeletonData(assetManager.get("source/" + activeName + ".json"));
    var skeleton = new spine.Skeleton(skeletonData);
    var bounds = calculateBounds(skeleton);

    // Create an AnimationState, and set the initial animation in looping mode.
    animationStateData = new spine.AnimationStateData(skeleton.data);
    var animationState = new spine.AnimationState(animationStateData);

    animationState.setAnimation(0, animationName, loop);

    animationState.addListener({
        start: function(track) {
            console.log("Animation on track " + track.trackIndex + " started");
            window.webkit.messageHandlers.animationMethod.postMessage("start");
            sp.nslog('1', 'start');
        },
        interrupt: function(track) {
            isPlaying = false;
            console.log("Animation on track " + track.trackIndex + " interrupted");
            window.webkit.messageHandlers.animationMethod.postMessage("interrupt");
            sp.nslog('0', 'interrupt');
        },
        end: function(track) {
            isPlaying = false;
            console.log("Animation on track " + track.trackIndex + " ended");
            window.webkit.messageHandlers.animationMethod.postMessage("end");
            sp.nslog('0', 'end');
        },
        disposed: function(track) {
            isPlaying = false;
            console.log("Animation on track " + track.trackIndex + " disposed");
            window.webkit.messageHandlers.animationMethod.postMessage("disposed");
            sp.nslog('0', 'disposed');
        },
        complete: function(track) {
            isPlaying = false;
            alert("complete");
            console.log("Animation on track " + track.trackIndex + " completed");
            window.webkit.messageHandlers.animationMethod.postMessage("complete");
            sp.nslog('0', 'complete');
        },
        event: function(track, event) {
            isPlaying = false;
            console.log("Event on track " + track.trackIndex + ": " + JSON.stringify(event));
            sp.nslog('0', 'event');
        }
    })

    // Pack everything up and return to caller.
    return { skeleton: skeleton, state: animationState, bounds: bounds};
}

function calculateBounds(skeleton) {
    skeleton.setToSetupPose();
    skeleton.updateWorldTransform();
    var offset = new spine.Vector2();
    var size = new spine.Vector2();
    skeleton.getBounds(offset, size, []);
    return { offset: offset, size: size };
}

function render () {
    var now = Date.now() / 1000;
    var delta = now - lastFrameTime;
    lastFrameTime = now;

    // Update the MVP matrix to adjust for canvas size changes
    resize();

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Apply the animation state based on the delta time.
    var state = skeletons[activeName_].state;
    var skeleton = skeletons[activeName_].skeleton;
    var bounds = skeletons[activeName_].bounds;
    state.update(delta);
    state.apply(skeleton);
    skeleton.updateWorldTransform();

    // Bind the shader and set the texture and model-view-projection matrix.
    shader.bind();
    shader.setUniformi(spine.webgl.Shader.SAMPLER, 0);
    shader.setUniform4x4f(spine.webgl.Shader.MVP_MATRIX, mvp.values);

    // Start the batch and tell the SkeletonRenderer to render the active skeleton.
    batcher.begin(shader);

    skeletonRenderer.draw(batcher, skeleton);
    batcher.end();

    shader.unbind();
    requestAnimationFrame(render);
}

function resize () {
    var w = canvas.clientWidth;
    var h = canvas.clientHeight;
    var bounds = skeletons[activeName_].bounds;
    if (canvas.width != w || canvas.height != h) {
        canvas.width = w;
        canvas.height = h;
    }

    // magic
    var centerX = bounds.offset.x + bounds.size.x / 2;
    var centerY = bounds.offset.y + bounds.size.y / 2;
    var scaleX = bounds.size.x / canvas.width;
    var scaleY = bounds.size.y / canvas.height;
    var scale = Math.max(scaleX, scaleY) * 1.2;
    if (scale < 1) scale = 1;
    var width = canvas.width * scale;
    var height = canvas.height * scale;

    mvp.ortho2d(centerX - width / 2, centerY - height / 2, width, height);
    gl.viewport(0, 0, canvas.width, canvas.height);
}
